import { Op, Model } from 'sequelize'

class BaseModel extends Model {
  static async saveData(data, forceUpdate = false) {
    const primaryKey = this.primaryKeyAttribute

    if (data[primaryKey] && !forceUpdate) {
      const id = data[primaryKey]
      delete data[primaryKey]

      const instance = await this.findByPk(id, {
        attributes: [primaryKey]
      })

      if (!instance) {
        return null
      }

      await instance.update(data)
      return instance.toJSON()
    }

    const instance = await this.create(data)
    return instance.toJSON()
  }

  static async createManyData(data) {
    return this.bulkCreate(data)
  }

  static async updateOneBy(filter, data) {
    const options = { where: {} }

    if (filter) {
      const filterKeys = Object.keys(filter)
      filterKeys.forEach(key => {
        const condition = this.whereFunc(key, filter[key])
        if (!options.where[Op.and]) {
          options.where[Op.and] = []
        }
  
        options.where[Op.and].push(condition)
      })
    }

    const instance = await this.findOne(options)

    if (!instance) {
      return null
    }

    await instance.update(data)
    return instance.toJSON()
  }

  static async updateBy(filter, data) {
    const options = {  where: {} }

    if (filter) {
      const filterKeys = Object.keys(filter)
      filterKeys.forEach(key => {
        const condition = this.whereFunc(key, filter[key])
        if (!options.where[Op.and]) {
          options.where[Op.and] = []
        }
  
        options.where[Op.and].push(condition)
      })
    }

    const instances = await this.findAll(options)

    if (!instances || instances.length === 0) {
      return 0
    }

    const updatePromises = instances.map(async (instance) => {
      await instance.update(data)
    })

    await Promise.all(updatePromises)
    return instances.length
  }

  static async deleteByWhere(filter) {
    const options = {  where: {} }

    if (filter) {
      const filterKeys = Object.keys(filter)
      filterKeys.forEach(key => {
        const condition = this.whereFunc(key, filter[key])
        if (!options.where[Op.and]) {
          options.where[Op.and] = []
        }
  
        options.where[Op.and].push(condition)
      })
    }

    return this.destroy(options)
  }

  static async softDeleteRestoreByWhere(filter) {
    const instances = await this.findAll({ where: filter, paranoid: false })

    if (!instances || instances.length === 0) {
      return 0
    }

    const restorePromises = instances.map(async (instance) => {
      await instance.restore()
    })

    await Promise.all(restorePromises)
    return instances.length
  }

  static async getCount(filter) {
    const options = {  where: {}, include: [] }

    if (filter.where) {
      options.where = this.buildWhere(filter.where)
    }

    if (filter.include) {
      options.include = filter.include.map(v => ({ ...v, where: this.buildWhere(v.where || {}) }))
    }

    const count = await this.count(options)
    return count || 0
  }

  static async getSum(filter, column) {
    const options = { include: [] }

    if (filter.where) {
      options.where = this.buildWhere(filter.where)
    }

    if (filter.include) {
      options.include = filter.include.map(v => ({ ...v, where: this.buildWhere(v.where || {}) }))
    }

    const sum = await this.sum(column, options)
    return sum || 0
  }

  static async getById(id, columns = []) {
    const options = {}
    if (columns.length > 0) {
      options.attributes = columns
    }

    const data = await this.findByPk(id, options)
    
    return data
  }

  static async findOneBy(filter, columns = [], orderBy = []) {
    const options = { include: [], order: orderBy }

    if (columns.length > 0) {
      options.attributes = columns
    }

    if (filter.where) {
      options.where = this.buildWhere(filter.where)
    }

    if (filter.include) {
      options.include = filter.include.map(v => ({ ...v, where: this.buildWhere(v.where || {}) }))
    }

    const data = await this.findOne(options)
  
    return data
  }

  static async findOneByWithTrashed(filter, columns = ['*'], orderBy = []) {
    const options = { attributes: columns, where: filter, paranoid: false }

    if (orderBy.length > 0) {
      options.order = orderBy
    }

    return this.findOne(options)
  }

  static async getListWithTrashed(filter, columns = ['*'], page = 1, pageSize = 20, orderBy = [], total = true) {
    const options = { attributes: columns, where: filter, paranoid: false }

    if (orderBy.length > 0) {
      options.order = orderBy
    }

    if (page > 0) {
      options.offset = (page - 1) * pageSize
      options.limit = pageSize
    }

    const result = {}
    result.list = await this.findAll(options)

    if (total) {
      result.total_count = await this.getCount(filter)
    }

    return result
  }

  static async getList(filter, columns = [], page = 1, pageSize = 20, orderBy = [], groupBy = [], total = true) {
    const options = { include: [], order: orderBy, group: groupBy }

    if (columns.length > 0) {
      options.attributes = columns
    }

    if (filter.where) {
      options.where = this.buildWhere(filter.where)
    }

    if (filter.include) {
      options.include = filter.include.map(v => ({ ...v, where: this.buildWhere(v.where || {}) }))
    }

    if (filter.raw) {
      options.raw = true
    }

    if (page > 0) {
      options.offset = (page - 1) * pageSize
      options.limit = pageSize
    }

    const result = {}
    result.list = await this.findAll(options)

    if (total) {
      result.total_count = await this.getCount(filter)
    }

    return result
  }

  static buildWhere(filter) {
    const where = {}
    const filterKeys = Object.keys(filter)
  
    filterKeys.forEach(key => {
      const condition = this.whereFunc(key, filter[key])
      where[Op.and] = where[Op.and] || []
      where[Op.and].push(condition)
    })
  
    return where
  }
  
  /*
    const filter = {
      id: 1,
      other_id: ['<>', 1],
      name: ['like', '%小明%'],
      name: ['notLike', '%小明%'],
      year: ['or', [2022, 2023]],
      ids: ['in', [1, 2, 3]],
      ids1: ['notIn', [1, 2, 3]],
      age: [['>', 1], ['<', 10]],
      time: ['between', ['2023-01-01', '2023-01-30']]
      time1: ['notBetween', ['2023-01-01', '2023-01-30']]
      nickname: 'isnull',
      aa: [
        'or',
        { id:  1 },
        { username: ['like', '%小夏%'] }
      ]
    }
    
  */

  static whereFunc(key, value) {
    if (Array.isArray(value)) {
      const operator = value[0]
      const condition = value.slice(1)

      switch (operator) {
        case '<>':
          return { [key]: { [Op.ne]: condition }}
        case 'like':
          return { [key]: { [Op.like]: condition }}
        case 'notLike':
          return { [key]: { [Op.notLike]: condition }}
        case 'or':
        case 'and':
          if (Array.isArray(condition[0])) {
           return { [key]: { [Op.or]: condition[0] }}
          } else {
            return {
              [Op[operator]]: condition.map(cond => {
                const keys = Object.keys(cond)
                return this.whereFunc(keys[0], cond[keys[0]])
              })
            }
          }
        case 'in':
          return { [key]: { [Op.in]: condition[0] }}
        case 'notIn':
          return { [key]: { [Op.notIn]: condition[0] } }
        case '>':
          return { [key]: { [Op.gt]: condition[0] } }
        case '>=':
          return { [key]: { [Op.gte]: condition[0] } }
        case '<':
          return { [key]: { [Op.lt]: condition[0] } }
        case '<=':
          return { [key]: { [Op.lte]: condition[0] } }
        case 'between':
          return { [key]: { [Op.between]: condition[0] } }
        case 'notBetween':
          return { [key]: { [Op.notBetween]: condition[0] } }
        case 'isnull':
          return { [key]: { [Op.is]: null } }
        default:
          // 可能还有其他自定义操作符的处理
          break
      }
    } else if (typeof value === 'object') {
      // 处理嵌套的查询条件
      const nestedConditions = Object.entries(value).map(([op, cond]) => {
        return { [Op[op]]: cond }
      })

      return { [key]: { [Op.and]: nestedConditions } }
    } else {
      return { [key]: value }
    }
  }
}

export default BaseModel